/* 
 *	Copyright (C) 2009-2010 miaohua
 *	http://code.google.com/p/lrcshower/
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */

#include "Stdafx.h"
#include <Windows.h>
#include <cassert>
#include <fstream>
#include <algorithm>
#include "LrcAnalyzer.h"
#include "HelperFunctions.h"
using namespace std;

tstring BlankName = _T("     ");
CLrcAnalyzer::CLrcAnalyzer()
{
     // Initialize the key words processs map
     int Size = sizeof(Keywords)/sizeof(Keywords[0]);
     for( int i = 0; i < Size; ++i )
          m_KeywordsProc[Keywords[i]] = NULL;
     // Just need title & offset value
     m_KeywordsProc[Keywords[TITLE]] = &CLrcAnalyzer::SetTitle;
     m_KeywordsProc[Keywords[OFFSET]] = &CLrcAnalyzer::SetOffset;
        
     // Initialize the variables
     m_TimeOffset = 0;
	 m_MaxLenIdx = 0; 
     m_Duration = 0;
     m_Name = BlankName;

     // Parse error code
     m_LastParseErr = NoErr;
}

bool CLrcAnalyzer::AnalyzeLrcFile(const tstring & FullPath, bool Force)
{
     // Check lrc file's full path
     if( FullPath.empty() ) 
     {
         m_LastParseErr = FileNotExist;
         return false;
     }

     // It is the same file, do noting(Just in unforce)
     // If force flag is set, do parse unconditionally
     if( !Force )
     {
         if( m_LrcFileFullPath == FullPath ) 
         {
             m_LastParseErr = FileSame;
             return false;
         }
     }     

     // Check wether the lrc file exists
     if( !IsFileExist(FullPath) )
     {
         m_LastParseErr = FileNotExist;
         return false;
     }

     // Save the full path
     m_LrcFileFullPath = FullPath;

     // Clear the previous lsr buffer
     m_LrcBuf.clear();

     // Initialize the variables
     m_TimeOffset = 0;
	 m_MaxLenIdx = 0; 
     m_Name = BlankName;
     
     // Analyze the lrc file line by line
     // Open the lrc file by std::wifstream / ifstream
     ifstream lrc(m_LrcFileFullPath.c_str());
     if( !lrc )
     {
         m_LastParseErr = OpenFileErr;
         return false;
     }
         
     // Analyze the lrc file line by line
     string TmpBuf;
     tstring CurBuf;
     while( getline(lrc, TmpBuf) )
     {
            tstring Str;
#ifdef   UNICODE
            MultiBytes2WildStr(TmpBuf, Str);
#else
            Str = TmpBuf;
#endif
            TrimStr(Str);
            CurBuf = Str; // Add current get line into buffer

            if( CurBuf.size() <= 0 || CurBuf[0] != '[' ) continue;
            if( AnalyseOneLine(CurBuf) )
                CurBuf.clear();
     }

     // Sort the lrc buffer according to their start time
     sort(m_LrcBuf.begin(), m_LrcBuf.end(), LrcTimeCmp());
     // Add offset
     transform(m_LrcBuf.begin(), m_LrcBuf.end(), m_LrcBuf.begin(), (AddOffset(m_TimeOffset)));
     // If ther is no lrc line, or the firs line lrc's start time is not 0, then insert one into the buffer
     if( m_LrcBuf.size() <= 0 || m_LrcBuf.front().m_Time != 0 )
     {
         OneLineLrc One;
         One.m_Time = 0;
         One.m_Line = m_Name; 
         m_LrcBuf.insert(m_LrcBuf.begin(), One);
     }

     //Calc the duration for every lrc line
     CalcDuration();

     // No error
     m_LastParseErr = NoErr;

     return true;
}

void CLrcAnalyzer::CalcDuration()
{
     int size = m_LrcBuf.size() - 1;
     for( int i = 0; i < size; ++i )
     {
          m_LrcBuf[i].m_Duration = m_LrcBuf[i+1].m_Time - m_LrcBuf[i].m_Time;
          assert(m_LrcBuf[i].m_Duration >= 0 );

	      if( m_LrcBuf[i+1].m_Line.size() > m_LrcBuf[m_MaxLenIdx].m_Line.size() )
		      m_MaxLenIdx = i+1;
     }
     // Calculate last line lrc's duration
     m_LrcBuf[size].m_Duration = m_Duration - m_LrcBuf[size].m_Time;
     // If user haven't set the song's duration, then we must check it
     if( m_LrcBuf[size].m_Duration <= 0 )
         m_LrcBuf[size].m_Duration = 500;
}

bool CLrcAnalyzer::CheckKeyWord(const tstring & Word)
{
     // Check wether it is a key word
     basic_string <TCHAR>::size_type Index = Word.find(':');
     if( Index == tstring::npos ) return false;
     // Get key word
     tstring Key = Word.substr(0, Index);
     tstring Value = Word.substr(Index+1);
     TrimStr(Key);
     TrimStr(Value);
     // Check it, find process function
     if( m_KeywordsProc.find(Key) != m_KeywordsProc.end() )
     {   
         // Yes, we found it, then process it
         if( m_KeywordsProc[Key] )
             (this->*m_KeywordsProc[Key])(Value);
         return true;                   
     }
         
     // Can't find 
     return false;
}

bool CLrcAnalyzer::AnalyseOneLine(const tstring & Line)
{
     basic_string <TCHAR>::size_type  Pos = tstring::npos;   
     basic_string <TCHAR>::size_type  OldPos = tstring::npos;   
   
     while( (Pos = Line.find(_T("]"), Pos+1)) != tstring::npos )   
            OldPos = Pos;   
   
     if( OldPos == tstring::npos )   
         return false;   
    
     tstring LyricLine = Line.substr(OldPos+1);      
     if( LyricLine.empty() )
         LyricLine =  _T("Music...");   //if empty, then just add a space
     else
         TrimStr(LyricLine);  
 
     Pos = tstring::npos;   
     while( (Pos = Line.find(_T("["), Pos+1)) != tstring::npos )
     {   
            basic_string <TCHAR>::size_type TimePos = Line.find(_T("]"), Pos+1);
            if( TimePos == tstring::npos ) return false;
            tstring Word = Line.substr(Pos+1, TimePos-Pos-1);    
            // Check the key word at first
            if( CheckKeyWord(Word) )
                continue;
            // If it is not a key word, then it must be a time stamp
            int  Time = GetTime(Word);   
            if( Time >= 0 )
            {   
                OneLineLrc One;
                One.m_Time = Time;
                One.m_Line = LyricLine; 
                m_LrcBuf.push_back(One);
            }   
     }      

     return true;
}

int CLrcAnalyzer::GetTime(const tstring & Time)   
{   
     int TimeClk = 0;
     int Minute = 0;
     int Second = 0;
     int MiSecond = 0;
     TCHAR szMinute[10] = {0};
     TCHAR szSecond[10] = {0};
     TCHAR szMiSecond[10] = {0};

     // Assume the time stamp like this [00:22.01]
     tstring  Get;   
     basic_string <TCHAR>::size_type Pos = Time.find(_T(":"),0);   
    
     if( Pos == tstring::npos )   
         return -1;   
   
     // Get minutes
     if( Pos == 0 ) 
         Minute = 0;   
     else {   
         Get = Time.substr(0, Pos);   
         Minute = _ttoi(Get.c_str());    
     }   
     TimeClk = Minute*60*1000;

     // Get second(at most, we just retrieve 2 digits)
     Get = Time.substr(Pos+1);   
     if( Get.size() == 1 )      
         Get = Get.substr(0, 1);   
     else if( Get.size() >= 2 )   
         Get = Get.substr(0, 2);   
     else   
         return TimeClk;   
     Second = _ttoi(Get.c_str());   
     TimeClk += Second*1000;   

     // Get misecond
     Get = Time.substr(Pos+1);
     Pos = Get.find('.');
     if( Pos == tstring::npos )   
         return TimeClk;
     Get = Get.substr(Pos+1);   
     MiSecond = _ttoi(Get.c_str());   
     TimeClk += MiSecond;
        
     return TimeClk;   
}   

bool CLrcAnalyzer::SetOffset(const tstring & Offset)
{
     m_TimeOffset = _ttoi(Offset.c_str());
     m_TimeOffset *= -1;
     return true;
}

bool CLrcAnalyzer::SetTitle(const tstring & Title)
{
     if( !Title.empty() )
     {
         m_Name = Title;
         return true;
     }
     return false;
}

void CLrcAnalyzer::SetDuration(long long Duration)
{
     m_Duration = Duration;
}